﻿#define USE_NORMAL

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace XNAVE3D
{
    class Terrain
    {
        SpriteBatch spriteBatch;

        Texture2D areia;

        Texture2D texture;

        public static Color[] pixels;

        public static VertexPositionNormalTexture[] verts;

        VertexBuffer vertexBuffer;

        short[] index;

        IndexBuffer indexBuffer;

        int sizeW, sizeH;

        Matrix world, view, projection;

        Effect effect;

        Light light;

        Vector3 cameraPos;
        
        int LarguraTela = 800;

        int AlturaTela = 600;

        GraphicsDevice GraphicsDevice; 

#if USE_NORMAL
        VertexPositionColor[] normals;
        VertexBuffer normalBuffer;
#endif
        
        public Terrain(ContentManager Content, GraphicsDevice GraphicsDevice)
        {
            this.GraphicsDevice = GraphicsDevice;

            this.cameraPos = new Vector3(0, 100, 100);

            this.world = Matrix.Identity;

            this.view = Matrix.CreateLookAt(cameraPos, Vector3.Zero, Vector3.Up);

            this.projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
                LarguraTela / AlturaTela, 0.1f, 10000);
            
            this.light = new Light();
        
            spriteBatch = new SpriteBatch(GraphicsDevice);

            this.effect = Content.Load<Effect>("Effects/Effect100");

            this.areia = Content.Load<Texture2D>("Texturas/TexturaGrama");

            this.texture = Content.Load<Texture2D>("Texture/hmap");

            pixels = new Color[this.texture.Width * this.texture.Height];

            this.sizeW = this.texture.Width;

            this.sizeH = this.texture.Height;
            
            this.texture.GetData<Color>(pixels);

            verts = new VertexPositionNormalTexture[this.sizeW * this.sizeH];

            for (int i = 0; i < this.sizeH; i++)
            {
                for (int j = 0; j < sizeW; j++)
                {
                    int idx = i * sizeW + j;

                    Vector3 position = new Vector3(j * 1.5f - (sizeW / 2),
                                                   pixels[idx].B/10,
                                                   i * 1.5f - (this.sizeH / 2));
                    verts[idx] = new VertexPositionNormalTexture(position, 
                                                                      Vector3.Up,
                                                                      new Vector2(j / (float)(sizeW - 1),
                                                                                  i / (float)(sizeH - 1)));
                }
            }

            this.vertexBuffer = new VertexBuffer(GraphicsDevice,
                                                 typeof(VertexPositionNormalTexture),
                                                 verts.Length,
                                                 BufferUsage.WriteOnly);

            this.vertexBuffer.SetData<VertexPositionNormalTexture>(verts);

            this.index = new short[(sizeH - 1) * (sizeW - 1) * 2 * 3];

            int aux = 0;

            for (int i = 0; i < sizeH - 1; i++)
            {
                for (int j = 0; j < sizeW- 1; j++)
                {
                    int idx = i * sizeW + j;

                    this.index[aux++] = (short)(i * sizeW + j);
                    this.index[aux++] = (short)(i * sizeW + (j + 1));
                    this.index[aux++] = (short)((i + 1) * sizeW + j);

                    this.index[aux++] = (short)((i + 1) * sizeW + j);
                    this.index[aux++] = (short)(i * sizeW + (j + 1));
                    this.index[aux++] = (short)((i + 1) * sizeW + (j + 1));
                }
            }

            this.indexBuffer = new IndexBuffer(GraphicsDevice,
                                               IndexElementSize.SixteenBits,
                                               this.index.Length,
                                               BufferUsage.WriteOnly);

            this.indexBuffer.SetData<short>(this.index);


#if USE_NORMAL

            // Normal
            for (int i = 0; i < this.index.Length; i += 3)
            {
                Vector3 v1 = verts[this.index[i]].Position;
                Vector3 v2 = verts[this.index[i+1]].Position;
                Vector3 v3 = verts[this.index[i+2]].Position;

                Vector3 vA = v3 - v1;
                Vector3 vB = v2 - v1;
                Vector3 normal = Vector3.Cross(vA, vB);
                normal.Normalize();

                verts[this.index[i]].Normal += normal;
                verts[this.index[i+1]].Normal += normal;
                verts[this.index[i+2]].Normal += normal;
            }

            // normalizando
            for (int i = 0; i < verts.Length; i++)
                verts[i].Normal.Normalize();
            
            // para desenhar as normais
            this.normals = new VertexPositionColor[verts.Length * 2];

            for (int i = 0; i < verts.Length; i++)
            {
                this.normals[i * 2] = new VertexPositionColor(verts[i].Position, Color.Black);
                this.normals[i * 2 + 1] = new VertexPositionColor(verts[i].Position + verts[i].Normal, Color.Black);
            }

            this.normalBuffer = new VertexBuffer(GraphicsDevice, typeof(VertexPositionColor),
                this.normals.Length, BufferUsage.WriteOnly);
            this.normalBuffer.SetData<VertexPositionColor>(this.normals);
#endif
        }

        public void Update(GameTime gameTime)
        {            
            Input();
        }
        
        public void Draw(Matrix v, Matrix p)
        {
            GraphicsDevice.SetVertexBuffer(this.vertexBuffer);
            GraphicsDevice.Indices = this.indexBuffer;

            //GraphicsDevice.RasterizerState = RasterizerState.CullNone;

            //RasterizerState rs = new RasterizerState();
            //rs.CullMode = CullMode.None;
            //rs.FillMode = FillMode.WireFrame;
            //GraphicsDevice.RasterizerState = rs;

            effect.CurrentTechnique = effect.Techniques["Technique1"];
            effect.Parameters["Projection"].SetValue(p);
            effect.Parameters["View"].SetValue(v);
            effect.Parameters["World"].SetValue(this.world);
            effect.Parameters["colorTexture"].SetValue(this.areia);

            Matrix matInvTrans;
            matInvTrans = Matrix.Transpose(this.world);
            matInvTrans = Matrix.Invert(matInvTrans);
            effect.Parameters["Inverse"].SetValue(matInvTrans);

            effect.Parameters["light_pos"].SetValue(this.light.position);
            effect.Parameters["emissive"].SetValue(this.light.emissive);
            effect.Parameters["ambient"].SetValue(this.light.ambient);
            effect.Parameters["diffuse"].SetValue(this.light.diffuse);
            effect.Parameters["specular"].SetValue(this.light.specular);
            effect.Parameters["shininess"].SetValue(this.light.shininess);
            effect.Parameters["specularMaterial"].SetValue(this.light.specularMat);
            effect.Parameters["eye_pos"].SetValue(this.cameraPos);
           
            foreach (EffectPass pass in  this.effect.CurrentTechnique.Passes)
            {
                pass.Apply();

                GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionNormalTexture>(PrimitiveType.TriangleList,
                    verts, 0, verts.Length, this.index, 0, this.index.Length / 3);
            }

#if USE_NORMAL
#if DRAW_NORMAL
            GraphicsDevice.SetVertexBuffer(this.normalBuffer);

            foreach (EffectPass pass in this.effect.CurrentTechnique.Passes)
            {
                pass.Apply();
                
                GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.LineList, this.normals, 0, verts.Length, this.vertexBuffer.VertexDeclaration);
            }
#endif
#endif
        }

        void Input()
        {
            if (Keyboard.GetState().IsKeyDown(Keys.Right))
            {
                this.light.position[0]++;
            }
            if (Keyboard.GetState().IsKeyDown(Keys.Left))
            {
                this.light.position[0]--;
            }
            if (Keyboard.GetState().IsKeyDown(Keys.Up))
            {
                this.light.position[2]--;
            }
            if (Keyboard.GetState().IsKeyDown(Keys.Down))
            {
                this.light.position[2]++;
            }
            if (Keyboard.GetState().IsKeyDown(Keys.PageDown))
            {
                this.light.position[1]--;
            }
            if (Keyboard.GetState().IsKeyDown(Keys.PageUp))
            {
                this.light.position[1]++;
            }
        }
    }
}
